/*
 *   Copyright 2021 zzh
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */

package com.aduib.boot.common.util;

import com.aduib.boot.common.exception.IoException;
import com.aduib.boot.common.io.BOMInputStream;
import com.aduib.boot.common.io.FastByteArrayOutputStream;
import com.aduib.boot.common.io.StreamProgress;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PushbackInputStream;
import java.io.PushbackReader;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.Objects;

/**
 * @description: IOUtils
 * @author: zzh
 * @date: 2021/10/28 10:06
 */
public class IOUtils {
  /** 默认缓存大小 8192 */
  public static final int DEFAULT_BUFFER_SIZE = 2 << 12;
  /** 默认中等缓存大小 16384 */
  public static final int DEFAULT_MIDDLE_BUFFER_SIZE = 2 << 13;
  /** 默认大缓存大小 32768 */
  public static final int DEFAULT_LARGE_BUFFER_SIZE = 2 << 14;

  /** 数据流末尾 */
  public static final int EOF = -1;
  /**
   * 将Reader中的内容复制到Writer中 使用默认缓存大小，拷贝后不关闭Reader
   *
   * @param reader Reader
   * @param writer Writer
   * @return 拷贝的字节数
   * @throws IoException IO异常
   */
  public static long copy(Reader reader, Writer writer) throws IoException {
    return copy(reader, writer, DEFAULT_BUFFER_SIZE);
  }

  /**
   * 将Reader中的内容复制到Writer中，拷贝后不关闭Reader
   *
   * @param reader Reader
   * @param writer Writer
   * @param bufferSize 缓存大小
   * @return 传输的byte数
   * @throws IoException IO异常
   */
  public static long copy(Reader reader, Writer writer, int bufferSize) throws IoException {
    return copy(reader, writer, bufferSize, null);
  }

  /**
   * 将Reader中的内容复制到Writer中，拷贝后不关闭Reader
   *
   * @param reader Reader
   * @param writer Writer
   * @param bufferSize 缓存大小
   * @param streamProgress 进度处理器
   * @return 传输的byte数
   * @throws IoException IO异常
   */
  public static long copy(
      Reader reader, Writer writer, int bufferSize, StreamProgress streamProgress)
      throws IoException {
    char[] buffer = new char[bufferSize];
    long size = 0;
    int readSize;
    if (null != streamProgress) {
      streamProgress.start();
    }
    try {
      while ((readSize = reader.read(buffer, 0, bufferSize)) != EOF) {
        writer.write(buffer, 0, readSize);
        size += readSize;
        writer.flush();
        if (null != streamProgress) {
          streamProgress.progress(size);
        }
      }
    } catch (Exception e) {
      throw new IoException(e);
    }
    if (null != streamProgress) {
      streamProgress.finish();
    }
    return size;
  }

  /**
   * 拷贝流，使用默认Buffer大小，拷贝后不关闭流
   *
   * @param in 输入流
   * @param out 输出流
   * @return 传输的byte数
   * @throws IoException IO异常
   */
  public static long copy(InputStream in, OutputStream out) throws IoException {
    return copy(in, out, DEFAULT_BUFFER_SIZE);
  }

  /**
   * 拷贝流，拷贝后不关闭流
   *
   * @param in 输入流
   * @param out 输出流
   * @param bufferSize 缓存大小
   * @return 传输的byte数
   * @throws IoException IO异常
   */
  public static long copy(InputStream in, OutputStream out, int bufferSize) throws IoException {
    return copy(in, out, bufferSize, null);
  }

  /**
   * 拷贝流，拷贝后不关闭流
   *
   * @param in 输入流
   * @param out 输出流
   * @param bufferSize 缓存大小
   * @param streamProgress 进度条
   * @return 传输的byte数
   * @throws IoException IO异常
   */
  public static long copy(
      InputStream in, OutputStream out, int bufferSize, StreamProgress streamProgress)
      throws IoException {
    if (bufferSize <= 0) {
      bufferSize = DEFAULT_BUFFER_SIZE;
    }

    byte[] buffer = new byte[bufferSize];
    if (null != streamProgress) {
      streamProgress.start();
    }
    long size = 0;
    try {
      for (int readSize; (readSize = in.read(buffer)) != EOF; ) {
        out.write(buffer, 0, readSize);
        size += readSize;
        out.flush();
        if (null != streamProgress) {
          streamProgress.progress(size);
        }
      }
    } catch (IOException e) {
      throw new IoException(e);
    }
    if (null != streamProgress) {
      streamProgress.finish();
    }
    return size;
  }

  // --------------------------------------------------------------------------------------
  // getReader and getWriter start

  /**
   * 获得一个文件读取器，默认使用UTF-8编码
   *
   * @param in 输入流
   * @return BufferedReader对象
   */
  public static BufferedReader getUtf8Reader(InputStream in) {
    return getReader(in, CharsetUtils.CHARSET_UTF_8);
  }

  /**
   * 获得一个文件读取器
   *
   * @param in 输入流
   * @param charsetName 字符集名称
   * @return BufferedReader对象
   */
  public static BufferedReader getReader(InputStream in, String charsetName) {
    return getReader(in, Charset.forName(charsetName));
  }

  /**
   * 从{@link BOMInputStream}中获取Reader
   *
   * @param in {@link BOMInputStream}
   * @return {@link BufferedReader}
   */
  public static BufferedReader getReader(BOMInputStream in) {
    return getReader(in, in.getCharset());
  }

  /**
   * 获得一个Reader
   *
   * @param in 输入流
   * @param charset 字符集
   * @return BufferedReader对象
   */
  public static BufferedReader getReader(InputStream in, Charset charset) {
    if (null == in) {
      return null;
    }

    InputStreamReader reader;
    if (null == charset) {
      reader = new InputStreamReader(in);
    } else {
      reader = new InputStreamReader(in, charset);
    }

    return new BufferedReader(reader);
  }

  /**
   * 获得{@link BufferedReader}<br>
   * 如果是{@link BufferedReader}强转返回，否则新建。如果提供的Reader为null返回null
   *
   * @param reader 普通Reader，如果为null返回null
   * @return {@link BufferedReader} or null
   */
  public static BufferedReader getReader(Reader reader) {
    if (null == reader) {
      return null;
    }

    return (reader instanceof BufferedReader)
        ? (BufferedReader) reader
        : new BufferedReader(reader);
  }

  /**
   * 获得{@link PushbackReader}<br>
   * 如果是{@link PushbackReader}强转返回，否则新建
   *
   * @param reader 普通Reader
   * @param pushBackSize 推后的byte数
   * @return {@link PushbackReader}
   */
  public static PushbackReader getPushBackReader(Reader reader, int pushBackSize) {
    return (reader instanceof PushbackReader)
        ? (PushbackReader) reader
        : new PushbackReader(reader, pushBackSize);
  }

  /**
   * 获得一个Writer，默认编码UTF-8
   *
   * @param out 输入流
   * @return OutputStreamWriter对象
   */
  public static OutputStreamWriter getUtf8Writer(OutputStream out) {
    return getWriter(out, CharsetUtils.CHARSET_UTF_8);
  }

  /**
   * 获得一个Writer
   *
   * @param out 输入流
   * @param charsetName 字符集
   * @return OutputStreamWriter对象
   */
  public static OutputStreamWriter getWriter(OutputStream out, String charsetName) {
    return getWriter(out, Charset.forName(charsetName));
  }

  /**
   * 获得一个Writer
   *
   * @param out 输入流
   * @param charset 字符集
   * @return OutputStreamWriter对象
   */
  public static OutputStreamWriter getWriter(OutputStream out, Charset charset) {
    if (null == out) {
      return null;
    }

    if (null == charset) {
      return new OutputStreamWriter(out);
    } else {
      return new OutputStreamWriter(out, charset);
    }
  }
  // --------------------------------------------------------------------------------------
  // getReader and getWriter end

  /**
   * String 转为流
   *
   * @param content 内容
   * @param charsetName 编码
   * @return 字节流
   */
  public static ByteArrayInputStream toStream(String content, String charsetName) {
    return toStream(content, CharsetUtils.charset(charsetName));
  }

  /**
   * String 转为流
   *
   * @param content 内容
   * @param charset 编码
   * @return 字节流
   */
  public static ByteArrayInputStream toStream(String content, Charset charset) {
    if (content == null) {
      return null;
    }
    return toStream(StrUtils.bytes(content, charset));
  }

  /**
   * String 转为UTF-8编码的字节流流
   *
   * @param content 内容
   * @return 字节流
   */
  public static ByteArrayInputStream toUtf8Stream(String content) {
    return toStream(content, CharsetUtils.CHARSET_UTF_8);
  }

  /**
   * 文件转为流
   *
   * @param file 文件
   * @return {@link FileInputStream}
   */
  public static FileInputStream toStream(File file) {
    try {
      return new FileInputStream(file);
    } catch (FileNotFoundException e) {
      throw new IoException(e);
    }
  }

  /**
   * String 转为流
   *
   * @param content 内容bytes
   * @return 字节流
   */
  public static ByteArrayInputStream toStream(byte[] content) {
    if (content == null) {
      return null;
    }
    return new ByteArrayInputStream(content);
  }

  /**
   * 转换为{@link BufferedInputStream}
   *
   * @param in {@link InputStream}
   * @return {@link BufferedInputStream}
   */
  public static BufferedInputStream toBuffered(InputStream in) {
    return (in instanceof BufferedInputStream)
        ? (BufferedInputStream) in
        : new BufferedInputStream(in);
  }

  /**
   * 转换为{@link BufferedInputStream}
   *
   * @param in {@link InputStream}
   * @param bufferSize buffer size
   * @return {@link BufferedInputStream}
   */
  public static BufferedInputStream toBuffered(InputStream in, int bufferSize) {
    return (in instanceof BufferedInputStream)
        ? (BufferedInputStream) in
        : new BufferedInputStream(in, bufferSize);
  }

  /**
   * 转换为{@link BufferedOutputStream}
   *
   * @param out {@link OutputStream}
   * @return {@link BufferedOutputStream}
   */
  public static BufferedOutputStream toBuffered(OutputStream out) {
    return (out instanceof BufferedOutputStream)
        ? (BufferedOutputStream) out
        : new BufferedOutputStream(out);
  }

  /**
   * 转换为{@link BufferedOutputStream}
   *
   * @param out {@link OutputStream}
   * @param bufferSize buffer size
   * @return {@link BufferedOutputStream}
   */
  public static BufferedOutputStream toBuffered(OutputStream out, int bufferSize) {
    return (out instanceof BufferedOutputStream)
        ? (BufferedOutputStream) out
        : new BufferedOutputStream(out, bufferSize);
  }

  /**
   * 转换为{@link BufferedReader}
   *
   * @param reader {@link Reader}
   * @return {@link BufferedReader}
   */
  public static BufferedReader toBuffered(Reader reader) {
    return (reader instanceof BufferedReader)
        ? (BufferedReader) reader
        : new BufferedReader(reader);
  }

  /**
   * 转换为{@link BufferedReader}
   *
   * @param reader {@link Reader}
   * @param bufferSize buffer size
   * @return {@link BufferedReader}
   */
  public static BufferedReader toBuffered(Reader reader, int bufferSize) {
    return (reader instanceof BufferedReader)
        ? (BufferedReader) reader
        : new BufferedReader(reader, bufferSize);
  }

  /**
   * 转换为{@link BufferedWriter}
   *
   * @param writer {@link Writer}
   * @return {@link BufferedWriter}
   */
  public static BufferedWriter toBuffered(Writer writer) {
    return (writer instanceof BufferedWriter)
        ? (BufferedWriter) writer
        : new BufferedWriter(writer);
  }

  /**
   * 转换为{@link BufferedWriter}
   *
   * @param writer {@link Writer}
   * @param bufferSize buffer size
   * @return {@link BufferedWriter}
   */
  public static BufferedWriter toBuffered(Writer writer, int bufferSize) {
    return (writer instanceof BufferedWriter)
        ? (BufferedWriter) writer
        : new BufferedWriter(writer, bufferSize);
  }

  /**
   * 将{@link InputStream}转换为支持mark标记的流<br>
   * 若原流支持mark标记，则返回原流，否则使用{@link BufferedInputStream} 包装之
   *
   * @param in 流
   * @return {@link InputStream}
   */
  public static InputStream toMarkSupportStream(InputStream in) {
    if (null == in) {
      return null;
    }
    if (false == in.markSupported()) {
      return new BufferedInputStream(in);
    }
    return in;
  }

  /**
   * 转换为{@link PushbackInputStream}<br>
   * 如果传入的输入流已经是{@link PushbackInputStream}，强转返回，否则新建一个
   *
   * @param in {@link InputStream}
   * @param pushBackSize 推后的byte数
   * @return {@link PushbackInputStream}
   */
  public static PushbackInputStream toPushbackStream(InputStream in, int pushBackSize) {
    return (in instanceof PushbackInputStream)
        ? (PushbackInputStream) in
        : new PushbackInputStream(in, pushBackSize);
  }

  /**
   * 将指定{@link InputStream} 转换为{@link InputStream#available()}方法可用的流。<br>
   * 在Socket通信流中，服务端未返回数据情况下{@link InputStream#available()}方法始终为{@code 0}<br>
   * 因此，在读取前需要调用{@link InputStream#read()}读取一个字节（未返回会阻塞），一旦读取到了，{@link
   * InputStream#available()}方法就正常了。<br>
   * 需要注意的是，在网络流中，是按照块来传输的，所以 {@link InputStream#available()} 读取到的并非最终长度，而是此次块的长度。<br>
   * 此方法返回对象的规则为：
   *
   * <ul>
   *   <li>FileInputStream 返回原对象，因为文件流的available方法本身可用
   *   <li>其它InputStream 返回PushbackInputStream
   * </ul>
   *
   * @param in 被转换的流
   * @return 转换后的流，可能为{@link PushbackInputStream}
   */
  public static InputStream toAvailableStream(InputStream in) {
    if (in instanceof FileInputStream) {
      // FileInputStream本身支持available方法。
      return in;
    }

    final PushbackInputStream pushbackInputStream = toPushbackStream(in, 1);
    try {
      final int available = pushbackInputStream.available();
      if (available <= 0) {
        // 此操作会阻塞，直到有数据被读到
        int b = pushbackInputStream.read();
        pushbackInputStream.unread(b);
      }
    } catch (IOException e) {
      throw new IoException(e);
    }

    return pushbackInputStream;
  }

  /**
   * 从流中读取内容，读到输出流中，读取完毕后并不关闭流
   *
   * @param in 输入流
   * @param isClose 读取完毕后是否关闭流
   * @return 输出流
   * @throws IoException IO异常
   */
  public static FastByteArrayOutputStream read(InputStream in, boolean isClose) throws IoException {
    final FastByteArrayOutputStream out;
    if (in instanceof FileInputStream) {
      // 文件流的长度是可预见的，此时直接读取效率更高
      try {
        out = new FastByteArrayOutputStream(in.available());
      } catch (IOException e) {
        throw new IoException(e);
      }
    } else {
      out = new FastByteArrayOutputStream();
    }
    try {
      copy(in, out);
    } finally {
      if (isClose) {
        close(in);
      }
    }
    return out;
  }

  /**
   * 从流中读取内容，读取完毕后并不关闭流
   *
   * @param in 输入流，读取完毕后并不关闭流
   * @param charset 字符集
   * @return 内容
   * @throws IoException IO异常
   */
  public static String read(InputStream in, Charset charset) throws IoException {
    return StrUtils.str(readBytes(in), charset);
  }

  /**
   * 从流中读取bytes，读取完毕后关闭流
   *
   * @param in {@link InputStream}
   * @return bytes
   * @throws IoException IO异常
   */
  public static byte[] readBytes(InputStream in) throws IoException {
    return readBytes(in, true);
  }

  /**
   * 从流中读取bytes
   *
   * @param in {@link InputStream}
   * @param isClose 是否关闭输入流
   * @return bytes
   * @throws IoException IO异常
   */
  public static byte[] readBytes(InputStream in, boolean isClose) throws IoException {
    if (in instanceof FileInputStream) {
      // 文件流的长度是可预见的，此时直接读取效率更高
      final byte[] result;
      try {
        final int available = in.available();
        result = new byte[available];
        final int readLength = in.read(result);
        if (readLength != available) {
          throw new IOException(
              StrUtils.format("File length is [{}] but read [{}]!", available, readLength));
        }
      } catch (IOException e) {
        throw new IoException(e);
      } finally {
        if (isClose) {
          close(in);
        }
      }
      return result;
    }

    // 未知bytes总量的流
    return read(in, isClose).toByteArray();
  }

  /**
   * 读取指定长度的byte数组，不关闭流
   *
   * @param in {@link InputStream}，为null返回null
   * @param length 长度，小于等于0返回空byte数组
   * @return bytes
   * @throws IoException IO异常
   */
  public static byte[] readBytes(InputStream in, int length) throws IoException {
    if (null == in) {
      return null;
    }
    if (length <= 0) {
      return new byte[0];
    }

    byte[] b = new byte[length];
    int readLength;
    try {
      readLength = in.read(b);
    } catch (IOException e) {
      throw new IoException(e);
    }
    if (readLength > 0 && readLength < length) {
      byte[] b2 = new byte[readLength];
      System.arraycopy(b, 0, b2, 0, readLength);
      return b2;
    } else {
      return b;
    }
  }

  /**
   * 关闭<br>
   * 关闭失败不会抛出异常
   *
   * @param closeable 被关闭的对象
   */
  public static void close(Closeable closeable) {
    if (null != closeable) {
      try {
        closeable.close();
      } catch (Exception e) {
        // 静默关闭
      }
    }
  }

  // ---------------------------//
  /**
   * 对比两个流内容是否相同<br>
   * 内部会转换流为 {@link BufferedInputStream}
   *
   * @param input1 第一个流
   * @param input2 第二个流
   * @return 两个流的内容一致返回true，否则false
   * @throws IoException IO异常
   */
  public static boolean contentEquals(InputStream input1, InputStream input2) throws IoException {
    if (false == (input1 instanceof BufferedInputStream)) {
      input1 = new BufferedInputStream(input1);
    }
    if (false == (input2 instanceof BufferedInputStream)) {
      input2 = new BufferedInputStream(input2);
    }

    try {
      int ch = input1.read();
      while (EOF != ch) {
        int ch2 = input2.read();
        if (ch != ch2) {
          return false;
        }
        ch = input1.read();
      }

      int ch2 = input2.read();
      return ch2 == EOF;
    } catch (IOException e) {
      throw new IoException(e);
    }
  }

  /**
   * 对比两个Reader的内容是否一致<br>
   * 内部会转换流为 {@link BufferedInputStream}
   *
   * @param input1 第一个reader
   * @param input2 第二个reader
   * @return 两个流的内容一致返回true，否则false
   * @throws IoException IO异常
   */
  public static boolean contentEquals(Reader input1, Reader input2) throws IoException {
    input1 = getReader(input1);
    input2 = getReader(input2);

    try {
      int ch = input1.read();
      while (EOF != ch) {
        int ch2 = input2.read();
        if (ch != ch2) {
          return false;
        }
        ch = input1.read();
      }

      int ch2 = input2.read();
      return ch2 == EOF;
    } catch (IOException e) {
      throw new IoException(e);
    }
  }

  /**
   * 对比两个流内容是否相同，忽略EOL字符<br>
   * 内部会转换流为 {@link BufferedInputStream}
   *
   * @param input1 第一个流
   * @param input2 第二个流
   * @return 两个流的内容一致返回true，否则false
   * @throws IoException IO异常
   */
  public static boolean contentEqualsIgnoreEOL(Reader input1, Reader input2) throws IoException {
    final BufferedReader br1 = getReader(input1);
    final BufferedReader br2 = getReader(input2);

    try {
      String line1 = br1.readLine();
      String line2 = br2.readLine();
      while (line1 != null && line1.equals(line2)) {
        line1 = br1.readLine();
        line2 = br2.readLine();
      }
      return Objects.equals(line1, line2);
    } catch (IOException e) {
      throw new IoException(e);
    }
  }
}
